/*
 * Copyright (c) 2018 ARM Limited
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Copyright 2015, 2021 LabWare
 * Copyright 2014 Google, Inc.
 * Copyright (c) 2002-2005 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __REMOTE_GDB_HH__
#define __REMOTE_GDB_HH__


#include <cstdint>
#include <exception>
#include <map>
#include <string>
#include <vector>

#include "arch/generic/pcstate.hh"
#include "base/cprintf.hh"
#include "base/pollevent.hh"
#include "base/socket.hh"
#include "base/types.hh"
#include "cpu/pc_event.hh"
#include "gdbremote/signals.hh"
#include "sim/debug.hh"
#include "sim/eventq.hh"

/*
 * This file implements a client for the GDB remote serial protocol as
 * described in this official documentation:
 *
 * https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html
 */

namespace gem5
{

class System;
class ThreadContext;

class BaseRemoteGDB;
class HardBreakpoint;

/**
 * Concrete subclasses of this abstract class represent how the
 * register values are transmitted on the wire.  Usually each
 * architecture should define one subclass, but there can be more
 * if there is more than one possible wire format.  For example,
 * ARM defines both AArch32GdbRegCache and AArch64GdbRegCache.
 */
class BaseGdbRegCache
{
  public:

    /**
     * Return the pointer to the raw bytes buffer containing the
     * register values.  Each byte of this buffer is literally
     * encoded as two hex digits in the g or G RSP packet.
     *
     * @ingroup api_remote_gdb
     */
    virtual char *data() = 0;

    /**
     * Return the size of the raw buffer, in bytes
     * (i.e., half of the number of digits in the g/G packet).
     *
     * @ingroup api_remote_gdb
     */
    virtual size_t size() const = 0;

    /**
     * Fill the raw buffer from the registers in the ThreadContext.
     *
     * @ingroup api_remote_gdb
     */
    virtual void getRegs(ThreadContext*) = 0;

    /**
     * Set the ThreadContext's registers from the values
     * in the raw buffer.
     *
     * @ingroup api_remote_gdb
     */
    virtual void setRegs(ThreadContext*) const = 0;

    /**
     * Return the name to use in places like DPRINTF.
     * Having each concrete superclass redefine this member
     * is useful in situations where the class of the regCache
     * can change on the fly.
     *
     * @ingroup api_remote_gdb
     */
    virtual const std::string name() const = 0;

    /**
     * @ingroup api_remote_gdb
     */
    BaseGdbRegCache(BaseRemoteGDB *g) : gdb(g)
    {}
    virtual ~BaseGdbRegCache()
    {}

  protected:
    BaseRemoteGDB *gdb;
};

class BaseRemoteGDB
{
    friend class HardBreakpoint;
  public:

    /**
     * @ingroup api_remote_gdb
     * @{
     */

    /**
     * Interface to other parts of the simulator.
     */
    BaseRemoteGDB(System *system, ListenSocketConfig _listen_config);
    virtual ~BaseRemoteGDB();

    std::string name();

    void listen();
    void connect();

    const ListenSocket &hostSocket() const;

    void attach(int fd);
    void detach();
    bool isAttached() { return attached; }

    void addThreadContext(ThreadContext *_tc);
    void replaceThreadContext(ThreadContext *_tc);
    bool selectThreadContext(ContextID id);

    void trap(ContextID id, GDBSignal sig,const std::string& stopReason="");
    bool sendMessage(std::string message);
    //schedule a trap event with these properties
    void scheduleTrapEvent(ContextID id,GDBSignal type, int delta,
      std::string stopReason);
    /** @} */ // end of api_remote_gdb

    template <class GDBStub, class ...Args>
    static BaseRemoteGDB *
    build(ListenSocketConfig listen_config, Args... args)
    {
        if (listen_config)
            return new GDBStub(args..., listen_config);
        else
            return nullptr;
    }

  protected:
    /*
     * Connection to the external GDB.
     */

    /*
     * Asynchronous socket events and event handlers.
     *
     * These events occur asynchronously and are handled asynchronously
     * to main simulation loop - therefore they *shall not* interact with
     * rest of gem5.
     *
     * The only thing they do is to schedule a synchronous event at instruction
     * boundary to deal with the request.
     */
    void incomingData(int revent);
    void incomingConnection(int revent);

    template <void (BaseRemoteGDB::*F)(int revent)>
    class SocketEvent : public PollEvent
    {
      protected:
        BaseRemoteGDB *gdb;

      public:
        SocketEvent(BaseRemoteGDB *gdb, int fd, int e) :
            PollEvent(fd, e), gdb(gdb)
        {}

        void process(int revent) { (gdb->*F)(revent); }
    };

    typedef SocketEvent<&BaseRemoteGDB::incomingConnection>
        IncomingConnectionEvent;
    typedef SocketEvent<&BaseRemoteGDB::incomingData>
        IncomingDataEvent;

    friend IncomingConnectionEvent;
    friend IncomingDataEvent;

    IncomingConnectionEvent *incomingConnectionEvent;
    IncomingDataEvent *incomingDataEvent;

    ListenSocketPtr listener;

    // The socket commands come in through.
    int fd;

    // Transfer data to/from GDB.
    uint8_t getbyte();
    bool try_getbyte(uint8_t* c,int timeout=-1);//return true if successful
    void putbyte(uint8_t b);

    void recv(std::vector<char> &bp);
    void send(const char *data);
    void send(const std::string &data) { send(data.c_str()); }

    template <typename ...Args>
    void
    send(const char *format, const Args &...args)
    {
        send(csprintf(format, args...));
    }

    /*
     * Process commands from remote GDB. If simulation has been
     * stopped because of some kind of fault (as segmentation violation,
     * or SW trap), 'signum' is the signal value reported back to GDB
     * in "S" packet (this is done in trap()).
     */
    void processCommands(GDBSignal sig=GDBSignal::ZERO);

    /*
     * Simulator side debugger state.
     */
    bool attached = false;
    bool threadSwitching = false;

    System *sys;

    std::map<ContextID, ThreadContext *> threads;
    ThreadContext *tc = nullptr;

    BaseGdbRegCache *regCachePtr = nullptr;

    MemberEventWrapper<&BaseRemoteGDB::connect> connectEvent;
    MemberEventWrapper<&BaseRemoteGDB::detach>  disconnectEvent;

    class TrapEvent : public Event
    {
      protected:
        GDBSignal _type;
        ContextID _id;
        std::string _stopReason;
        BaseRemoteGDB *gdb;

      public:
        TrapEvent(BaseRemoteGDB *g) : gdb(g)
        {}

        void type(GDBSignal t) { _type = t; }
        void stopReason(std::string s) {_stopReason = s; }
        void id(ContextID id) { _id = id; }
         void process() { gdb->trap(_id, _type,_stopReason); }
    } trapEvent;

    /*
     * The interface to the simulated system.
     */
    virtual bool readBlob(Addr vaddr, size_t size, char *data);
    virtual bool writeBlob(Addr vaddr, size_t size, const char *data);
    bool read(Addr vaddr, size_t size, char *data);
    bool write(Addr vaddr, size_t size, const char *data);

    template <class T> T read(Addr addr);
    template <class T> void write(Addr addr, T data);

    // Single step.
    void singleStep();
    MemberEventWrapper<&BaseRemoteGDB::singleStep> singleStepEvent;

    void clearSingleStep();
    void setSingleStep();

    /// Schedule an event which will be triggered "delta" instructions later.
    void scheduleInstCommitEvent(Event *ev, int delta,ThreadContext* _tc);
    void scheduleInstCommitEvent(Event *ev, int delta){
       scheduleInstCommitEvent(ev, delta,tc);
    };
    /// Deschedule an instruction count based event.
    void descheduleInstCommitEvent(Event *ev);

    // Breakpoints.
    virtual void insertSoftBreak(Addr addr, size_t kind);
    virtual void removeSoftBreak(Addr addr, size_t kind);
    virtual void insertHardBreak(Addr addr, size_t kind);
    virtual void removeHardBreak(Addr addr, size_t kind);

    void sendTPacket(GDBSignal sig, ContextID id,
      const std::string& stopReason);
    void sendSPacket(GDBSignal sig);
    //The OPacket allow to send string to be displayed by the remote GDB
    void sendOPacket(const std::string message);
    /*
     * GDB commands.
     */
    struct GdbCommand
    {
      public:
        struct Context
        {
            const GdbCommand *cmd;
            char cmdByte;
            GDBSignal type;
            char *data;
            int len;
        };

        typedef bool (BaseRemoteGDB::*Func)(Context &ctx);

        const char * const name;
        const Func func;

        GdbCommand(const char *_name, Func _func) : name(_name), func(_func) {}
    };

    static std::map<char, GdbCommand> commandMap;

    struct GdbMultiLetterCommand
    {
      public:
        struct Context
        {
            const GdbMultiLetterCommand *cmd;
            std::string cmdTxt;
            GDBSignal type;
            char *data;
            int len;
        };

        typedef bool (BaseRemoteGDB::*Func)(Context &ctx);

        const char * const name;
        const Func func;

        GdbMultiLetterCommand(const char *_name, Func _func) :
          name(_name), func(_func) {}
    };


    static std::map<std::string, GdbMultiLetterCommand> multiLetterMap;

    bool cmdUnsupported(GdbCommand::Context &ctx);

    bool cmdSignal(GdbCommand::Context &ctx);
    bool cmdCont(GdbCommand::Context &ctx);
    bool cmdAsyncCont(GdbCommand::Context &ctx);
    bool cmdDetach(GdbCommand::Context &ctx);
    bool cmdRegR(GdbCommand::Context &ctx);
    bool cmdRegW(GdbCommand::Context &ctx);
    bool cmdSetThread(GdbCommand::Context &ctx);
    bool cmdIsThreadAlive(GdbCommand::Context &ctx);
    bool cmdMemR(GdbCommand::Context &ctx);
    bool cmdMemW(GdbCommand::Context &ctx);
    bool cmdQueryVar(GdbCommand::Context &ctx);
    bool cmdStep(GdbCommand::Context &ctx);
    bool cmdAsyncStep(GdbCommand::Context &ctx);
    bool cmdClrHwBkpt(GdbCommand::Context &ctx);
    bool cmdSetHwBkpt(GdbCommand::Context &ctx);
    bool cmdDumpPageTable(GdbCommand::Context &ctx);
    bool cmdMultiLetter(GdbCommand::Context &ctx);

    //Multi letter command
    bool cmdMultiUnsupported(GdbMultiLetterCommand::Context &ctx);

    bool cmdReplyEmpty(GdbMultiLetterCommand::Context &ctx);
    bool cmdVKill(GdbMultiLetterCommand::Context &ctx);

    struct QuerySetCommand
    {
        struct Context
        {
            const std::string &name;
            std::vector<std::string> args;

            Context(const std::string &_name) : name(_name) {}
        };

        using Func = bool (BaseRemoteGDB::*)(Context &ctx);

        const char * const argSep;
        const Func func;

        QuerySetCommand(Func _func, const char *_argSep=nullptr) :
            argSep(_argSep), func(_func)
        {}
    };

    static std::map<std::string, QuerySetCommand> queryMap;

    bool queryC(QuerySetCommand::Context &ctx);
    bool querySupported(QuerySetCommand::Context &ctx);
    bool queryXfer(QuerySetCommand::Context &ctx);
    bool querySymbol(QuerySetCommand::Context &ctx);
    bool queryRcmd(QuerySetCommand::Context &ctx);
    bool queryAttached(QuerySetCommand::Context &ctx);

    size_t threadInfoIdx = 0;
    bool queryFThreadInfo(QuerySetCommand::Context &ctx);
    bool querySThreadInfo(QuerySetCommand::Context &ctx);

  protected:
    ThreadContext *context() { return tc; }
    System *system() { return sys; }

    void encodeBinaryData(const std::string &unencoded,
            std::string &encoded) const;

    void encodeXferResponse(const std::string &unencoded,
        std::string &encoded, size_t offset, size_t unencoded_length) const;

    // checkBpKind checks if a kind of breakpoint is legal. This function should
    // be implemented by subclasses by arch. The "kind" is considered to be
    // breakpoint size in some arch.
    virtual bool checkBpKind(size_t kind);

    virtual BaseGdbRegCache *gdbRegs() = 0;

    virtual bool acc(Addr addr, size_t len) = 0;

    virtual std::vector<std::string> availableFeatures() const;

    /**
     * Get an XML target description.
     *
     * @param[in] annex the XML filename
     * @param[out] output set to the decoded XML
     * @return true if the given annex was found
     */
    virtual bool getXferFeaturesRead(const std::string &annex,
            std::string &output);
};

template <class T>
inline T
BaseRemoteGDB::read(Addr addr)
{
    T temp;
    read(addr, sizeof(T), (char *)&temp);
    return temp;
}

template <class T>
inline void
BaseRemoteGDB::write(Addr addr, T data)
{
    write(addr, sizeof(T), (const char *)&data);
}

} // namespace gem5

#endif /* __REMOTE_GDB_H__ */
